#include "Mocker.h"

namespace MockItNow
{

namespace
{
	bool ShouldReplayFunctionCallAccordingToRecord(FunctionCallRecord* record, size_t address, size_t context)
	{
		if (record == 0)
			return false;

		if (record->GetFunctionAddress() != address)
			return false;

		if (record->GetIgnoreTarget() == false && (size_t)record->GetRecordTarget() != 0 && (size_t)record->GetRecordTarget() != context)
			return false;

		return true;
	}
}

MockerInterface* MockerInterface::m_interfaceInstance = 0;
Mocker* Mocker::m_instance = 0;

Mocker& Mocker::Instance()
{
	if (m_instance == 0)
		throw FailureException("Mocker instance is null! Did you forget to create a mocker?");

	return *m_instance;
}

bool Mocker::IsAlive()
{
	return m_instance != 0;
}

Mocker::Mocker(bool verifyOnDestruction)
: m_verifyOnDestruction(verifyOnDestruction),
  m_state(Startup),
  m_currentFunctionInfo(0),
  m_nextReplayFunctionCallRecord(0)
{
	MOCKITNOW_ASSERT(m_instance == 0);
	m_instance = this;
	m_interfaceInstance = this;
}

Mocker::~Mocker()
{
	MOCKITNOW_ASSERT(m_instance == this);
	m_instance = 0;
	m_interfaceInstance = 0;

	if (m_verifyOnDestruction == true && m_state != Verified)
		Verify();

	SetFailureContext(0);
}

void Mocker::Clear()
{
	m_registeredFunctions.Clear();
	m_functionCallRecords.Clear();
	m_defaultReturnRecords.Clear();
}

void Mocker::SetFunctionName(const char* name)
{
	MOCKITNOW_ASSERT(m_state == Startup);
	MOCKITNOW_ASSERT(m_registeredFunctions.GetTail() != NULL);

	FunctionInfo* lastRegisteredFunction = m_registeredFunctions.GetTail();
	lastRegisteredFunction->name = name;
}

void Mocker::DefaultStub()
{
	MOCKITNOW_ASSERT(m_state == Startup);
	MOCKITNOW_ASSERT(m_registeredFunctions.GetTail() != NULL);

	const FunctionInfo* lastRegisteredFunction = m_registeredFunctions.GetTail();

	if (lastRegisteredFunction->hasReturnValue == true)
		ReportFailure("Trying to set a default stub for a function with a return value, use DefaultMock");

	FunctionCallRecord* record = new FunctionCallRecord(lastRegisteredFunction, 0, 0);
	record->IgnoreArguments();

	m_defaultReturnRecords.Add(record);
}

void Mocker::Ignore(int arguments)
{
	MOCKITNOW_ASSERT(m_state == Recording);

	FunctionCallRecord* record = m_functionCallRecords.GetTail();

	if (record == 0)
		ReportFailure("You can't call Ignore() before recording the function! Did you forget to register it?");

	for (int i = 0; i < record->GetNumArguments(); ++i)
	{
		if ((arguments & (1 << i)) > 0)
			record->IgnoreArgument(i);
	}

	if (arguments && This)
	{
		record->SetIgnoreTarget();
	}
}

void Mocker::FailWhenCalled()
{
	MOCKITNOW_ASSERT(m_state == Recording);

	if (m_functionCallRecords.GetTail() == 0)
		ReportFailure("You can't call FailWhenCalled() before recording the function! Did you forget to register it?");

	m_functionCallRecords.GetTail()->SetFailWhenCalled();
}

void Mocker::Record()
{
	MOCKITNOW_ASSERT(m_state == Startup);
	m_state = Recording;
}

void Mocker::Replay()
{
	MOCKITNOW_ASSERT(m_state == Recording);
	m_state = Replaying;
	m_nextReplayFunctionCallRecord = m_functionCallRecords.GetHead();
}

bool Mocker::Verify()
{
	while (m_nextReplayFunctionCallRecord != 0 && m_nextReplayFunctionCallRecord->GetFailWhenCalled())
		m_nextReplayFunctionCallRecord = m_nextReplayFunctionCallRecord->next;

	if (m_nextReplayFunctionCallRecord != 0)
	{
		SetFailureContext(m_nextReplayFunctionCallRecord->GetFunctionInfo());
		ReportFailure("Expected function was never called");
	}

	FunctionCallRecord* record = m_functionCallRecords.GetHead();

	while (record != 0)
	{
		SetFailureContext(record->GetFunctionInfo());
		const std::string& reportedError = record->GetReportedFailure();
		if (reportedError.length() != 0)
			ReportFailure(reportedError);
		record = record->next;
	}	m_state = Verified;

	Clear();

	return true;
}

bool Mocker::IsRecording() const
{
	return m_state == Recording;
}

bool Mocker::ShouldStubFunction(size_t address, size_t context)
{
	switch (m_state)
	{
		case Startup:
		case Verified:
			return false;

		case Recording:
			return ShouldRecordFunctionCall(address);

		case Replaying:
			return ShouldReplayFunctionCall(address, context);

		default:
			MOCKITNOW_ASSERT(false);
	}

	return false;
}

void Mocker::SetCurrentFunctionAddress(size_t address)
{
	m_currentFunctionInfo = FindFunctionInfo(address);
	SetFailureContext(m_currentFunctionInfo);
}

size_t Mocker::GetFunctionJumpAddress() const
{
	MOCKITNOW_ASSERT(m_state == Recording || m_state == Replaying);
	MOCKITNOW_ASSERT(m_currentFunctionInfo != 0);

	if (m_state == Recording)
		return m_currentFunctionInfo->recordFunctionAddress;
	else
		return m_currentFunctionInfo->replayFunctionAddress;
}

void Mocker::CallOriginal()
{
	MOCKITNOW_ASSERT(m_state == Recording);

	FunctionCallRecord* record = m_functionCallRecords.GetTail();
	if (record == NULL)
	{
		SetFailureContext(NULL);
		ReportFailure("You can't call CallOriginal() before recording the function! Did you forget to register it? Is it overloaded?");
	}

	record->CallOriginal();
}

FunctionCallRecord* Mocker::RegisterRecordFunctionCall(int numArguments, void* target)
{
	MOCKITNOW_ASSERT(m_state == Recording);
	MOCKITNOW_ASSERT(m_currentFunctionInfo != 0);

	FunctionCallRecord* record = new FunctionCallRecord(m_currentFunctionInfo, target, numArguments);
	m_functionCallRecords.Add(record);

	return record;
}

FunctionCallRecord* Mocker::RegisterReplayFunctionCall(void* target)
{
	MOCKITNOW_ASSERT(m_state == Replaying);
	MOCKITNOW_ASSERT(m_currentFunctionInfo != 0);

	FunctionCallRecord* record = m_nextReplayFunctionCallRecord;
	if (m_nextReplayFunctionCallRecord != NULL && record->GetFunctionAddress() == m_currentFunctionInfo->mockFunctionAddress && (record->GetIgnoreTarget() || record->GetRecordTarget() == target))
	{
		if (record->GetFailWhenCalled() == true)
			ReportFailure("Detected a call to a function that shouldn't be called");

		m_nextReplayFunctionCallRecord = m_nextReplayFunctionCallRecord->next;

		return record;
	}

	record = FindReturnAlwaysFunctionCall(m_currentFunctionInfo->mockFunctionAddress, (int)target);

	if (record != NULL)
		return record;

	record = FindDefaultReturnFunctionCall(m_currentFunctionInfo->mockFunctionAddress);

	if (record == NULL)
		ReportFailure("Attempt to replay function that does not have a default stub");

	return record;
}

void Mocker::RegisterFunction(size_t mockFunctionAddress, size_t recordFunctionAddress, size_t replayFunctionAddress, bool hasReturnValue)
{
	FunctionInfo* info = new FunctionInfo();
	info->mockFunctionAddress = mockFunctionAddress;
	info->recordFunctionAddress = recordFunctionAddress;
	info->replayFunctionAddress = replayFunctionAddress;
	info->hasReturnValue = hasReturnValue;

	m_registeredFunctions.Add(info);

	SetFailureContext(info);
}

const FunctionInfo* Mocker::FindFunctionInfo(size_t address) const
{
	FunctionInfo* info = m_registeredFunctions.GetHead();

	while (info != 0)
	{
		if (info->mockFunctionAddress == address)
			return info;

		info = info->next;
	}

	return 0;	
}

FunctionCallRecord* Mocker::FindDefaultReturnFunctionCall(size_t address)
{
	FunctionCallRecord* record = m_defaultReturnRecords.GetHead();

	while (record != 0)
	{
		if (record->GetFunctionAddress() == address)
			return record;

		record = record->next;
	}

	return NULL;
}

FunctionCallRecord* Mocker::FindReturnAlwaysFunctionCall(size_t address, size_t context)
{
	FunctionCallRecord* record = m_functionCallRecords.GetHead();

	while (record != NULL)
	{
		if (record->GetReturnAlways() && ShouldReplayFunctionCallAccordingToRecord(record, address, context))
			return record;

		record = record->next;
	}

	return NULL;
}

bool Mocker::ShouldReplayFunctionCall(size_t address, size_t context)
{
	FunctionCallRecord* nextRecord = m_nextReplayFunctionCallRecord;

	while(nextRecord != 0)
	{
		if (nextRecord->GetReturnAlways() == false)
		{
			if (ShouldReplayFunctionCallAccordingToRecord(nextRecord, address, context) == true)
			{
				if (nextRecord->GetCallOriginal())
				{
					m_nextReplayFunctionCallRecord  = nextRecord->next;
					return false;
				}

				m_nextReplayFunctionCallRecord = nextRecord;
				return true;
			}

			if (nextRecord->GetFailWhenCalled() == false)
				break;
		}

		nextRecord = nextRecord->next;
	}

	return HasReturnAlways(address, context) || HasDefaultReturn(address);
}

bool Mocker::ShouldRecordFunctionCall(size_t address)
{
	if (FindFunctionInfo(address) == 0)
		return false;

	return true;
}

bool Mocker::HasDefaultReturn(size_t address)
{
	if (FindDefaultReturnFunctionCall(address) == 0)
		return false;

	return true;
}

bool Mocker::HasReturnAlways(size_t address, size_t context)
{
	return FindReturnAlwaysFunctionCall(address, context) != NULL;
}

void Mocker::SetVerifyOnDestruction(bool value)
{
	m_verifyOnDestruction = value;
}

}
